//build: 20240716
//TCP Client
import { tcpClientClass } from './libvbar-m-dxtcp.so'
import std from './dxStd.js'
import dxMap from './dxMap.js'
import dxCommon from './dxCommon.js'
import bus from './dxEventBus.js'
const map = dxMap.get("default")
const tcpObj = new tcpClientClass();
const tcp = {}

/**
 * Create a client connection
 * @param {string} ip Server IP address is required.
 * @param {string} port Server port (required)
 * @param {number} timeout Timeout, the default is 5000.
 * @param {number} heartEn Heartbeat (1 for on, 0 for off) Default is 1
 * @param {number} heartTime Heart rate interval / second, default is 60 seconds
 * @param {string} id Handle ID, optional (a unique ID is required if creating multiple instances).
 */
tcp.create = function (ip, port, timeout = 5000, heartEn = 1, heartTime = 1, id) {
    if (!ip) {
        throw new Error("create:ip should not be null or empty")
    }
    if (!port) {
        throw new Error("create:port should not be null or empty")
    }
    let pointer = tcpObj.tcpClientCreate(1, ip, port, timeout, heartEn, heartTime)
    if (!pointer) {
        throw new Error("tcp.create: create failed")
    }
    dxCommon.handleId("tcp", id, pointer)

}
/**
 * Receive data and return a Uint8Array.
 * @param {number} len Data length (required)
 * @param {number} timeout_ms Timeout period (required)
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns array data
 */
tcp.receive = function (len, timeout_ms, id) {
    if (!len) {
        throw new Error("receive:len should not be null or empty")
    }
    if (!timeout_ms) {
        throw new Error("receive:timeout_ms should not be null or empty")
    }
    let pointer = dxCommon.handleId("tcp", id)
    let res = tcpObj.tcpClientRecv(pointer, len, timeout_ms)
    if (res == null) {
        return null
    }
    return new Uint8Array(res)
}

/**
 * Send data
 * @param {string} data Send content (string), required.
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns true/false
 */
tcp.send = function (data, id) {
    if (!data) {
        throw new Error("data should not be null or empty")
    }
    let pointer = dxCommon.handleId("tcp", id)
    return tcpObj.tcpClientSend(pointer, data)
}

/**
 * Data is sent using the low-light communication protocol format.
 * @param {string} data Send content (string), required.
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns true/false
 */
tcp.sendVg = function (data, id) {
    if (typeof data === 'string') {
        return tcp.sendBuffer(dxCommon.hexStringToArrayBuffer(data), id)
    }
    let pack = '55aa' + data.cmd
    if (data.hasOwnProperty('result')) {
        pack += data.result
    }
    pack += (data.length % 256).toString(16).padStart(2, '0')
    pack += (Math.floor(data.length / 256)).toString(16).padStart(2, '0')
    pack += data.data
    let all = dxCommon.hexToArr(pack)
    let bcc = dxCommon.calculateBcc(all)
    all.push(bcc)
    return tcp.sendBuffer(new Uint8Array(all).buffer, id)
}

/**
 * Send ArrayBuffer
 * @param {ArrayBuffer} data Send content (ArrayBuffer), required.
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns true/false
 */
tcp.sendBuffer = function (data, id) {
    if (!data) {
        throw new Error("data should not be null or empty")
    }
    let pointer = dxCommon.handleId("tcp", id)
    return tcpObj.tcpClientSendBuffer(pointer, data)
}

/**
 * Determine if a TCP connection is established
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns true/false
 */
tcp.isConnect = function (id) {
    let pointer = dxCommon.handleId("tcp", id)
    return tcpObj.tcpClientIsConnect(pointer)
}

/**
 * Refresh Channel
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns true/false
 */
tcp.flush = function (id) {
    let pointer = dxCommon.handleId("tcp", id)
    return tcpObj.tcpClientFlush(pointer)
}

/**
 * destroy
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns true/false
 */
tcp.destory = function (id) {
    let pointer = dxCommon.handleId("tcp", id)
    return tcpObj.tcpClientDestory(pointer)
}

tcp.VG = {
    RECEIVE_MSG: '__tcpvg__MsgReceive',
    CONNECTED_CHANGED: '__tcp__Connect_changed',
}
/**
 * Simplify the use of the low-light communication protocol:
 * 1. Receive Data: After receiving the binary data from the TLV, parse it into an object and send it out as an event from the eventcenter (tcp.VG.RECEIVE_MSG).
 * Returned object format: {cmd:"2a",result:"01",length:7,data:"0a1acc320fee32",bcc:true}
 * cmd: A 1-byte command word, a hexadecimal string.
 * result: A 1-byte identifier word indicating the result of data processing, success, failure, or other status. Only feedback data has an identifier, a hexadecimal string.
 * length: The length of the data, defined as 2 bytes in TLV, here directly converted to a decimal number.
 * data: A multi-byte data field, a hexadecimal string.
 * bcc: BCC checksum success or failure.
 * 2. Sending Data: Convert the object into TLV format binary data before sending it. This can be done using the `tcp.send` method. The data format is as follows:
 * There are two data formats to send:
        1. Object format: `{cmd:"2a",result:"01",length:7,data:"0a1acc320fee32"}`
        2. Complete hexadecimal string: `'55AA09000000F6'`
 * 3. With the same ID, multiple calls to `runvg` will only execute once.
 * 
 * @param {object} options Startup parameters
 *          @param {string} options.ip Server IP address is required.
 *          @param {string} options.port Server port (required)
 *          @param {number} options.timeout Timeout, the default is 5000.
 *          @param {number} options.heartEn Heartbeat (1 for on, 0 for off) Default is 1
 *          @param {number} options.heartTime Heart rate interval / second, default is 60 seconds
 *          @param {number} options.result 0 and 1 (default is 0) indicate whether the received data or the transmitted data contains the identifier byte. 0 means the received data does not include the identifier byte, and the transmitted data does; 1 means the opposite.
 *          @param {number} options.passThrough If passThrough is set to true, the received data will use the pass-through mode. This field is not required.
 *          @param {string} options.id  Handle ID, optional (a unique ID is required if initializing multiple instances).
 */
tcp.runvg = function (options) {
    if (options === undefined || options.length === 0) {
        throw new Error("dxtcp.runvg:'options' parameter should not be null or empty")
    }
    if (options.id === undefined || options.id === null || typeof options.id !== 'string') {
        // handle id
        options.id = ""
    }
    if (options.ip === undefined || options.ip === null) {
        throw new Error("dxtcp.runvg:'ip' should not be null or empty")
    }
    if (options.port === undefined || options.port === null) {
        throw new Error("dxtcp.runvg:'port' should not be null or empty")
    }
    let oldfilepre = '/app/code/dxmodules/vgTcpWorker'
    let content = std.loadFile(oldfilepre + '.js').replace("{{id}}", options.id)
    let newfile = oldfilepre + options.id + '.js'
    std.saveFile(newfile, content)
    let init = map.get("__vgtcp__run_init" + options.id)
    if (!init) {//Ensure initialization only once
        map.put("__vgtcp__run_init" + options.id, options)
        bus.newWorker(options.id || "__vgtcp", newfile)
    }
}
/**
 * Get the current TCP connection status
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns 'connected' or 'disconnected'
 */
tcp.getConnected = function (id) {
    if (id == undefined || id == null) {
        id = ""
    }
    return tcp.isConnect(id) ? "connected" : "disconnected"
}
export default tcp;